跳到主要内容

聊天室:添加好友弹窗、通知页面

上节实现了好友、群聊的列表,这节来实现添加好友功能

添加 src/pages/Friendship/AddFriendModal.tsx

import { Button, Form, Input, InputNumber, Modal, message } from "antd";
import { useForm } from "antd/es/form/Form";
import TextArea from "antd/es/input/TextArea";
import { useState } from "react";

interface AddFriendModalProps {
isOpen: boolean;
handleClose: Function;
}

const layout = {
labelCol: { span: 6 },
wrapperCol: { span: 18 },
};

export interface AddFriend {
username: string;
reason: string;
}

export function AddFriendModal(props: AddFriendModalProps) {
const [form] = useForm<AddFriend>();

const handleOk = async function () {};

return (
<Modal
title="添加好友"
open={props.isOpen}
onOk={handleOk}
onCancel={() => props.handleClose()}
okText={"发送好友请求"}
cancelText={"取消"}>
<Form form={form} colon={false} {...layout}>
<Form.Item
label="用户名"
name="username"
rules={[{ required: true, message: "请输入用户名!" }]}>
<Input />
</Form.Item>
<Form.Item
label="添加理由"
name="reason"
rules={[{ required: true, message: "请输入添加理由!" }]}>
<TextArea />
</Form.Item>
</Form>
</Modal>
);
}

然后在 Friednship/index.tsx 里引入下:

添加一个 state 控制弹窗显示隐藏,然后加一个按钮,点击的时候设置 state 为 true,关闭弹窗的时候设置 state 为 false

const [isAddFriendModalOpen, setAddFriendModalOpen] = useState(false);
<Form.Item label=" ">
<Button
type="primary"
style={{ background: "green" }}
onClick={() => setAddFriendModalOpen(true)}>
添加好友
</Button>
</Form.Item>
<AddFriendModal
isOpen={isAddFriendModalOpen}
handleClose={() => {
setAddFriendModalOpen(false);
}}
/>

测试下:

然后调用下添加好友的接口。

之前是通过 id 来添加的好友:

现在要改一下:

import { IsNotEmpty } from "class-validator";

export class FriendAddDto {
@IsNotEmpty({
message: "添加好友的 username 不能为空",
})
username: string;

reason: string;
}

然后改下 service 的实现:

async add(friendAddDto: FriendAddDto, userId: number) {
const friend = await this.prismaService.user.findUnique({
where: {
username: friendAddDto.username
}
});

if(!friend) {
throw new BadRequestException('要添加的 username 不存在');
}

if(friend.id === userId) {
throw new BadRequestException('不能添加自己为好友');
}

const found = await this.prismaService.friendship.findMany({
where: {
userId,
friendId: friend.id
}
})

if(found.length) {
throw new BadRequestException('该好友已经添加过');
}

return await this.prismaService.friendRequest.create({
data: {
fromUserId: userId,
toUserId: friend.id,
reason: friendAddDto.reason,
status: 0
}
})
}

先根据 username 查询 user,如果不存在就返回错误,提示 username 不存在。

如果添加的是自己,返回错误,提示不能添加自己为好友。

如果已经添加过,返回错误,提示已经添加。

否则,创建好友申请。

在页面调用下:

interfaces 加一下这个接口

export async function friendAdd(data: AddFriend) {
return axiosInstance.post("/friendship/add", data);
}

组件里调用下:

const handleOk = async function () {
await form.validateFields();

const values = form.getFieldsValue();

try {
const res = await friendAdd(values);

if (res.status === 201 || res.status === 200) {
message.success("好友申请已发送");
form.resetFields();
props.handleClose();
}
} catch (e: any) {
message.error(e.response?.data?.message || "系统繁忙,请稍后再试");
}
};

试下效果:

提示好友申请已发送。

其中 hong 提示已经是好友了,我们查一下:

/friendship/list

确实。

然后查一下新的好友请求:

已经有了。

然后我们写一下通知页面:

之前的好友请求列表接口有点问题:

其实用户发出的好友请求、发给用户的好友请求,都应该展示出来。

并且接口应该顺带把用户信息也给查出来返回。

我们完善下:

async list(userId: number) {
const fromMeRequest = await this.prismaService.friendRequest.findMany({
where: {
fromUserId: userId
}
})

const toMeRequest = await this.prismaService.friendRequest.findMany({
where: {
toUserId: userId
}
})

const res = {
toMe: [],
fromMe: []
}

for (let i = 0; i < fromMeRequest.length; i++) {
const user = await this.prismaService.user.findUnique({
where: {
id: fromMeRequest[i].toUserId
},
select: {
id: true,
username: true,
nickName: true,
email: true,
headPic: true,
createTime: true
}
})
res.fromMe.push({
...fromMeRequest[i],
toUser: user
})
}

for (let i = 0; i < toMeRequest.length; i++) {
const user = await this.prismaService.user.findUnique({
where: {
id: toMeRequest[i].fromUserId
},
select: {
id: true,
username: true,
nickName: true,
email: true,
headPic: true,
createTime: true
}
})
res.toMe.push({
...toMeRequest[i],
fromUser: user
})
}

return res;
}

分别查询 fromUserId、toUsrId 为 userId 的好友请求,然后把其中的 user 查出来返回。

测试下:

因为现在还没有发送给当前用户的好友请求。

我们在界面发送一个:

再查询就有了:

然后我们在页面把这个显示下就行:

src/pages/Notification.tsx

import {
Button,
Form,
Input,
Popconfirm,
Table,
Tabs,
TabsProps,
message,
} from "antd";
import { useForm } from "antd/es/form/Form";
import "./index.css";

export function Notification() {
const [form] = useForm();

const onChange = (key: string) => {
console.log(key);
};

const items: TabsProps["items"] = [
{
key: "1",
label: "我发出的",
children: "发给我的",
},
{
key: "2",
label: "我发出的",
children: "我发出的",
},
];

return (
<div id="notification-container">
<div className="notification-list">
<Tabs defaultActiveKey="1" items={items} onChange={onChange} />
</div>
</div>
);
}

css

#notification-container {
padding: 20px;
}

然后在 interfaces 添加下接口:

export async function friendRequestList() {
return axiosInstance.get("/friendship/request_list");
}

在页面调用下:

import {
Button,
Form,
Input,
Popconfirm,
Table,
Tabs,
TabsProps,
message,
} from "antd";
import { useForm } from "antd/es/form/Form";
import "./index.css";
import { useEffect, useState } from "react";
import { friendRequestList } from "../../interfaces";

interface User {
id: number;
headPic: string;
nickName: string;
email: string;
captcha: string;
}

interface FriendRequest {
id: number;
fromUserId: number;
toUserId: number;
reason: string;
createTime: Date;
fromUser: User;
toUser: User;
status: number;
}

export function Notification() {
const [form] = useForm();
const [fromMe, setFromMe] = useState<Array<FriendRequest>>([]);
const [toMe, setToMe] = useState<Array<FriendRequest>>([]);

async function queryFriendRequestList() {
try {
const res = await friendRequestList();

if (res.status === 201 || res.status === 200) {
setFromMe(
res.data.fromMe.map((item: FriendRequest) => {
return {
...item,
key: item.id,
};
})
);
setToMe(
res.data.toMe.map((item: FriendRequest) => {
return {
...item,
key: item.id,
};
})
);
}
} catch (e: any) {
message.error(e.response?.data?.message || "系统繁忙,请稍后再试");
}
}

useEffect(() => {
queryFriendRequestList();
}, []);

const onChange = (key: string) => {
console.log(key);
};

const items: TabsProps["items"] = [
{
key: "1",
label: "我发出的",
children: (
<div style={{ width: 1000 }}>{JSON.stringify(fromMe)}</div>
),
},
{
key: "2",
label: "发给我的",
children: <div style={{ width: 1000 }}>{JSON.stringify(toMe)}</div>,
},
];

return (
<div id="notification-container">
<div className="notification-list">
<Tabs defaultActiveKey="1" items={items} onChange={onChange} />
</div>
</div>
);
}

请求下接口,设置到 fromMe、toMe 的 state:

然后在 tab 内容展示下:

看下效果:

数据请求成功。

我们用 table 展示下就好了:

import {
Button,
Form,
Input,
Popconfirm,
Table,
Tabs,
TabsProps,
message,
} from "antd";
import { useForm } from "antd/es/form/Form";
import "./index.css";
import { useEffect, useMemo, useState } from "react";
import { friendRequestList } from "../../interfaces";
import { ColumnsType } from "antd/es/table";

interface User {
id: number;
headPic: string;
nickName: string;
email: string;
captcha: string;
}

interface FriendRequest {
id: number;
fromUserId: number;
toUserId: number;
reason: string;
createTime: Date;
fromUser: User;
toUser: User;
status: number;
}

export function Notification() {
const [form] = useForm();
const [fromMe, setFromMe] = useState<Array<FriendRequest>>([]);
const [toMe, setToMe] = useState<Array<FriendRequest>>([]);

async function queryFriendRequestList() {
try {
const res = await friendRequestList();

if (res.status === 201 || res.status === 200) {
setFromMe(
res.data.fromMe.map((item: FriendRequest) => {
return {
...item,
key: item.id,
};
})
);
setToMe(
res.data.toMe.map((item: FriendRequest) => {
return {
...item,
key: item.id,
};
})
);
}
} catch (e: any) {
message.error(e.response?.data?.message || "系统繁忙,请稍后再试");
}
}

useEffect(() => {
queryFriendRequestList();
}, []);

const onChange = (key: string) => {
console.log(key);
};

const toMeColumns: ColumnsType<FriendRequest> = [
{
title: "用户",
render: (_, record) => {
return (
<div>
<img
src={record.fromUser.headPic}
width={30}
height={30}
/>
{" " + record.fromUser.nickName + " 请求加你为好友"}
</div>
);
},
},

{
title: "请求时间",
render: (_, record) => {
return new Date(record.createTime).toLocaleString();
},
},
{
title: "操作",
render: (_, record) => (
<div>
<a href="#">同意</a>
<br />
<a href="#">拒绝</a>
</div>
),
},
];

const fromMeColumns: ColumnsType<FriendRequest> = [
{
title: "用户",
render: (_, record) => {
return (
<div>
{" 请求添加好友 " + record.toUser.nickName}
<img
src={record.toUser.headPic}
width={30}
height={30}
/>
</div>
);
},
},

{
title: "请求时间",
render: (_, record) => {
return new Date(record.createTime).toLocaleString();
},
},
{
title: "状态",
render: (_, record) => {
const map: Record<string, any> = {
0: "申请中",
1: "已通过",
2: "已拒绝",
};
return <div>{map[record.status]}</div>;
},
},
];

const items: TabsProps["items"] = [
{
key: "1",
label: "发给我的",
children: (
<div style={{ width: 1000 }}>
<Table
columns={toMeColumns}
dataSource={toMe}
style={{ width: "1000px" }}
/>
</div>
),
},
{
key: "2",
label: "我发出的",
children: (
<div style={{ width: 1000 }}>
<Table
columns={fromMeColumns}
dataSource={fromMe}
style={{ width: "1000px" }}
/>
</div>
),
},
];

return (
<div id="notification-container">
<div className="notification-list">
<Tabs defaultActiveKey="1" items={items} onChange={onChange} />
</div>
</div>
);
}

看下效果:

没啥问题。

然后加一下同意和拒绝的接口调用:

export async function agreeFriendRequest(id: number) {
return axiosInstance.get(`/friendship/agree/${id}`);
}

export async function rejectFriendRequest(id: number) {
return axiosInstance.get(`/friendship/reject/${id}`);
}

然后页面上调用下:

{
title: '操作',
render: (_, record) => {
if(record.status === 0) {
return <div>
<a href="#" onClick={() => agree(record.fromUserId)}>同意</a><br/>
<a href="#" onClick={() => reject(record.fromUserId)}>拒绝</a>
</div>
} else {
const map: Record<string, any> = {
1: '已通过',
2: '已拒绝'
}
return <div>
{map[record.status]}
</div>
}
}
}
async function agree(id: number) {
try {
const res = await agreeFriendRequest(id);

if (res.status === 201 || res.status === 200) {
message.success("操作成功");
queryFriendRequestList();
}
} catch (e: any) {
message.error(e.response?.data?.message || "系统繁忙,请稍后再试");
}
}

async function reject(id: number) {
try {
const res = await rejectFriendRequest(id);

if (res.status === 201 || res.status === 200) {
message.success("操作成功");
queryFriendRequestList();
}
} catch (e: any) {
message.error(e.response?.data?.message || "系统繁忙,请稍后再试");
}
}

试下效果:

同意后再看下好友列表:

多了小强这个好友。

我们好像忘记展示 reason 了,补一下:

最后,我们整体测试下添加好友的功能:

首先登录一个用户的账号,给 guang 发送好友请求:

然后登录 guang 的账号:

同意之后,就可以在好友列表看到这个好友了。

前端代码

后端代码

总结

这节我们实现了添加好友。

首先点击添加好友按钮的时候会有个弹窗,输入 username 和理由之后,会发送一个好友请求。

在通知页面分别展示发给我的和我发出的好友请求,对方点击同意后,就会成为好友了。

这样添加好友、好友请求的功能就完成了。